package com.doan.covua;

import java.util.ArrayList;

import android.util.Log;

import com.doan.covua.gamelogic.Move;
import com.doan.covua.gamelogic.MoveGen;
import com.doan.covua.gamelogic.Position;
import com.doan.covua.gamelogic.TextIO;
import com.doan.covua.gamelogic.UndoInfo;

public class ChessEngine {

	Book book;
	NativePipedProcess npp = null;
	public ChessEngine(){
		if (npp == null) {
    		npp = new NativePipedProcess();
    		npp.initialize();
    		npp.writeLineToProcess("uci");
    		npp.writeLineToProcess("setoption name Hash value 16");
    		npp.writeLineToProcess("setoption name Ponder value false");
    		//npp.writeLineToProcess(String.format("setoption name Threads value %d", nThreads));
    		npp.writeLineToProcess("ucinewgame");
    		syncReady();
	 }
		book = new Book();
	}
	private final void syncReady() {
    	npp.writeLineToProcess("isready");
    	while (true) {
    		String s = npp.readLineFromProcess(1000);
    		if (s.equals("readyok"))
    			break;
    	}
    }
	/** Convert a string to tokens by splitting at whitespace characters. */
    private final String[] tokenize(String cmdLine) {
        cmdLine = cmdLine.trim();
        return cmdLine.split("\\s+");
    }
	 /** Wait for engine to respond with "bestmove". While waiting, monitor and report search info. */
	private final String monitorEngine(Position pos) {
		// Monitor engine response
    	clearInfo();
    	boolean stopSent = false;
    	while (true) {	
			int timeout = 2000;
    		while (true) {
    			if (shouldStop && !stopSent) {
    		    	npp.writeLineToProcess("stop");
    		    	stopSent = true;
    			}
    			String s = npp.readLineFromProcess(timeout);
    			if (s.length() == 0)
    				break;
    			String[] tokens = tokenize(s);
    			if (tokens[0].equals("info")) {
    				parseInfoCmd(tokens);
    			} else if (tokens[0].equals("bestmove")) {
    				Log.v("bestmove...","" + tokens[1]);
    				return tokens[1];
    			}
    			timeout = 0;
    		}
    		
    		//notifyGUI(pos);
    		
			try {
				Thread.sleep(100); // 10 GUI updates per second is enough
			} catch (InterruptedException e) {
			}
    	}
	}
	 public final String doSearch(Position prevPos, ArrayList<Move> mList, Position currPos, boolean drawOffer,
			 int wTime, int bTime, int inc, int movesToGo){
		 Log.v("da vao do search","khung khung");
		 long[] posHashList = new long[mList.size() + 1];
		 int posHashListSize = 0;
		
		 Position p = new Position(prevPos);
		 UndoInfo ui = new UndoInfo();
		 for(int i = 0; i < mList.size(); i++){
			 Log.v("hello ","111");
			 posHashList[posHashListSize++] = p.zobristHash();
			 p.makeMove(mList.get(i), ui);
		 }
		 Move bookMove = book.getBookMove(currPos);
		 if (bookMove != null) {
	        	if (canClaimDraw(currPos, posHashList, posHashListSize, bookMove) == "") {
	        		return TextIO.moveToString(currPos, bookMove, false);
	        	}
	        }

    	// If only one legal move, play it without searching
        ArrayList<Move> moves = new MoveGen().pseudoLegalMoves(currPos);
        moves = MoveGen.removeIllegal(currPos, moves);
        if (moves.size() == 0) {        	 
        	return ""; // User set up a position where computer has no valid moves.
        }
        if (moves.size() == 1) {      	 
        	Move bestMove = moves.get(0);
        	if (canClaimDraw(currPos, posHashList, posHashListSize, bestMove) == "") {
        		return TextIO.moveToUCIString(bestMove);
        	}
        }
        //START
        StringBuilder posStr = new StringBuilder();
    	posStr.append("position fen ");
    	posStr.append(TextIO.toFEN(prevPos));
    	int nMoves = mList.size();
    	if (nMoves > 0) {
    		posStr.append(" moves");
    		for (int i = 0; i < nMoves; i++) {
    			posStr.append(" ");
    			posStr.append(TextIO.moveToUCIString(mList.get(i)));
    		}
    	}
    	npp.writeLineToProcess(posStr.toString());
    	
    	if (wTime < 1) wTime = 1;
    	if (bTime < 1) bTime = 1;
    	String goStr = String.format("go wtime %d btime %d", wTime, bTime);
    	if (inc > 0)
    		goStr += String.format(" winc %d binc %d", inc, inc);
    	if (movesToGo > 0) {
    		goStr += String.format(" movestogo %d", movesToGo);
    	}
    	npp.writeLineToProcess(goStr);

    	String bestMove = monitorEngine(currPos);
    	
    	 // Claim draw if appropriate
        if (statScore <= 0) {
        	String drawClaim = canClaimDraw(currPos, posHashList, posHashListSize, TextIO.UCIstringToMove(bestMove));
        	if (drawClaim != "")
        		bestMove = drawClaim;
        }
       
        // Accept draw offer if engine is losing
    	
        if (drawOffer && !statIsMate && (statScore <= -300)) {
        	bestMove = "draw accept";
        }
       
        Log.v("best move ","" + bestMove);
        //END
//        bestmove = generateMove(currPos);
        return bestMove;
	 }
	 /** Check if a draw claim is allowed, possibly after playing "move".
	     * @param move The move that may have to be made before claiming draw.
	     * @return The draw string that claims the draw, or empty string if draw claim not valid.
	     */
    private String canClaimDraw(Position pos, long[] posHashList, int posHashListSize, Move move) {
    	String drawStr = "";
        if (canClaimDraw50(pos)) {
            drawStr = "draw 50";
        } else if (canClaimDrawRep(pos, posHashList, posHashListSize, posHashListSize)) {
            drawStr = "draw rep";
        } else {
            String strMove = TextIO.moveToString(pos, move, false);
            posHashList[posHashListSize++] = pos.zobristHash();
            UndoInfo ui = new UndoInfo();
            pos.makeMove(move, ui);
            if (canClaimDraw50(pos)) {
                drawStr = "draw 50 " + strMove;
            } else if (canClaimDrawRep(pos, posHashList, posHashListSize, posHashListSize)) {
                drawStr = "draw rep " + strMove;
            }
            pos.unMakeMove(move, ui);
        }
        return drawStr;
    }
    
    private final static boolean canClaimDraw50(Position pos) {
        return (pos.halfMoveClock >= 100);
    }
    private final static boolean canClaimDrawRep(Position pos, long[] posHashList, int posHashListSize, int posHashFirstNew) {
        int reps = 0;
        for (int i = posHashListSize - 4; i >= 0; i -= 2) {
            if (pos.zobristHash() == posHashList[i]) {
                reps++;
                if (i >= posHashFirstNew) {
                    reps++;
                    break;
                }
            }
        }
        return (reps >= 2);
    }
	private int statCurrDepth = 0;
	private int statPVDepth = 0;
    private int statScore = 0;
    private boolean statIsMate = false;
    private boolean statUpperBound = false;
    private boolean statLowerBound = false;
    private int statTime = 0;
    private int statNodes = 0;
    private int statNps = 0;
    private ArrayList<String> statPV = new ArrayList<String>();
    private String statCurrMove = "";
    private int statCurrMoveNr = 0;

    private boolean depthModified = false;
    private boolean currMoveModified = false;
    private boolean pvModified = false;
    private boolean statsModified = false;

    private final void clearInfo() {
    	depthModified = false;
        currMoveModified = false;
        pvModified = false;
        statsModified = false;
    }
    private final void parseInfoCmd(String[] tokens) {
    	try {
    		int nTokens = tokens.length;
    		int i = 1;
    		while (i < nTokens - 1) {
    			String is = tokens[i++];
    			if (is.equals("depth")) {
    				statCurrDepth = Integer.parseInt(tokens[i++]);
    				depthModified = true;
    			} else if (is.equals("currmove")) {
    				statCurrMove = tokens[i++];
    				currMoveModified = true;
    			} else if (is.equals("currmovenumber")) {
    				statCurrMoveNr = Integer.parseInt(tokens[i++]);
    				currMoveModified = true;
    			} else if (is.equals("time")) {
    				statTime = Integer.parseInt(tokens[i++]);
    				statsModified = true;
    			} else if (is.equals("nodes")) {
    				statNodes = Integer.parseInt(tokens[i++]);
    				statsModified = true;
    			} else if (is.equals("nps")) {
    				statNps = Integer.parseInt(tokens[i++]);
    				statsModified = true;
    			} else if (is.equals("pv")) {
    				statPV.clear();
    				while (i < nTokens)
    					statPV.add(tokens[i++]);
    				pvModified = true;
    				statPVDepth = statCurrDepth;
    			} else if (is.equals("score")) {
					tokens[i++].equals("mate");
					statScore = Integer.parseInt(tokens[i++]);
					statUpperBound = false;
					statLowerBound = false;
					if (tokens[i].equals("upperbound")) {
						statUpperBound = true;
						i++;
					} else if (tokens[i].equals("lowerbound")) {
						statLowerBound = true;
						i++;
					}
    				pvModified = true;
    			}
    		}
    	} catch (NumberFormatException nfe) {
    		// Ignore
    	} catch (ArrayIndexOutOfBoundsException aioob) {
    		// Ignore
    	}
	}
    public boolean shouldStop = false;
    public final void stopSearch() {
    	shouldStop = true;
    	npp.writeLineToProcess("stop");
    }
    
}
